package com.abing.sandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RuntimeUtil;
import com.abing.sandbox.enums.ExecuteCodeStatusEnum;
import com.abing.sandbox.request.ExecuteCodeRequest;
import com.abing.sandbox.request.ExecuteCodeResponse;
import com.abing.sandbox.request.JudgeInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author CaptainBing
 * @Date 2024/9/6 11:47
 * @Description 默认代码沙箱
 */
@Slf4j
public class DefaultCodeSandbox implements CodeSandbox {

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {

        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        String code = executeCodeRequest.getCode();

        // 1.保存代码文件
        final String globalCodeDirName = "codetemplates";
        String userDir = System.getProperty("user.dir");
        String globalCodePathName = userDir + File.separator + globalCodeDirName;
        // 判断全局代码目录是否存在，没有则新建
        if (!FileUtil.exist(globalCodePathName)) {
            FileUtil.mkdir(globalCodePathName);
        }
        final String globalJavaClassName = "Main.java";
        // 把用户的代码隔离存放
        String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
        String userCodePath = userCodeParentPath + File.separator + globalJavaClassName;
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

        // 2.编译文件
        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
        Process compileProcess = RuntimeUtil.exec(compileCmd);
        try {
            int exitValue = compileProcess.waitFor();
            if (exitValue != 0) {
                String errorResult = RuntimeUtil.getResult(compileProcess);
                executeCodeResponse.setStatus(ExecuteCodeStatusEnum.COMPILE_FAILED.getValue());
                executeCodeResponse.setMessage(errorResult);
                return executeCodeResponse;
            }
        } catch (InterruptedException e) {
            return getErrorResponse(e);
        }

        // 3.执行文件
        List<String> inputs = executeCodeRequest.getInputs();
        List<String> outputList = new ArrayList<>();
        List<JudgeInfo> judgeInfoList = new ArrayList<>();

        for (String input : inputs) {

            String runCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeParentPath, input);
            JudgeInfo judgeInfo = new JudgeInfo();
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();

            long totalMemory = RuntimeUtil.getTotalMemory();
            Process runProcess = RuntimeUtil.exec(runCmd);
            try {
                boolean exitValueStatus = runProcess.waitFor(1, TimeUnit.SECONDS);
                stopWatch.stop();
                long freeMemory = RuntimeUtil.getFreeMemory();
                if (exitValueStatus) {
                    List<String> successResult = RuntimeUtil.getResultLines(runProcess,StandardCharsets.UTF_8);
                    judgeInfo.setTime(stopWatch.getLastTaskTimeMillis());
                    judgeInfo.setMemory((totalMemory - freeMemory) / 1024);
                    judgeInfo.setStatus(ExecuteCodeStatusEnum.SUCCESS.getValue());
                    outputList.addAll(successResult);
                } else {
                    String errorResult = RuntimeUtil.getResult(runProcess,StandardCharsets.UTF_8);
                    judgeInfo.setTime(stopWatch.getLastTaskTimeMillis());
                    judgeInfo.setMemory((totalMemory - freeMemory) / 1024);
                    judgeInfo.setStatus(ExecuteCodeStatusEnum.TIME_OUT.getValue());
                    judgeInfo.setMessage(errorResult);
                }
                judgeInfoList.add(judgeInfo);
            } catch (InterruptedException e) {
                return getErrorResponse(e);
            }
        }

        // 4.删除文件
        if (userCodeFile.getParentFile() != null) {
            FileUtil.del(userCodeParentPath);
        }

        executeCodeResponse.setStatus(ExecuteCodeStatusEnum.SUCCESS.getValue());
        executeCodeResponse.setMessage(ExecuteCodeStatusEnum.SUCCESS.getText());
        executeCodeResponse.setJudgeInfo(judgeInfoList);
        executeCodeResponse.setOutputList(outputList);
        return executeCodeResponse;
    }

    /**
     * 响应错误信息
     * @param e
     * @return
     */
    private ExecuteCodeResponse getErrorResponse(Throwable e) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        // 表示代码沙箱错误
        executeCodeResponse.setStatus(ExecuteCodeStatusEnum.COMPILE_FAILED.getValue());
        executeCodeResponse.setJudgeInfo(new ArrayList<>());
        return executeCodeResponse;
    }


    public static void main(String[] args) {
        DefaultCodeSandbox defaultCodeSandBox = new DefaultCodeSandbox();
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputs(Arrays.asList("1 2","2 4","4 4"));
        executeCodeRequest.setCode("public class Main {\n" +
                                           "\n" +
                                           "    public static void main(String[] args) {\n" +
                                           "\n" +
                                           "        Scanner scanner = new Scanner(System.in);\n" +
                                           "        while (scanner.hasNext()) {\n" +
                                           "            int a = scanner.nextInt();\n" +
                                           "            int b = scanner.nextInt();\n" +
                                           "            int c = a + b;\n" +
                                           "            System.out.println(c);\n" +
                                           "        }\n" +
                                           "    }\n" +
                                           "}");
        ExecuteCodeResponse executeCodeResponse = defaultCodeSandBox.executeCode(executeCodeRequest);
        System.out.println("executeCodeResponse = " + executeCodeResponse);
    }
}
